package com.deng.mina.common.protocol;

import com.deng.mina.common.util.LogUtils;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.AttributeKey;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolDecoderAdapter;
import org.apache.mina.filter.codec.ProtocolDecoderOutput;

import java.nio.charset.Charset;

/**
 * @Project
 * @Module
 * @JDK_Vserion 1.8
 * @Author DengLibin
 * @Email dlb@smarthse.cn
 * @Desc 解码器
 * @since 2019/9/18 0018 9:53
 */
public class ProtocolDecoder extends ProtocolDecoderAdapter {
    private final AttributeKey CONTEXT = new AttributeKey(this.getClass(), "context");
    private  Charset charset;
    private int maxPackLength = 1 << 7; // 包大小限制
    private final  int packHeadLength = 5; //包头大小
    public ProtocolDecoder(Charset charset){
        this.charset = charset;
    }
    /**
     * 解码 （粘包处理）
     * @param session
     * @param in
     * @param out
     * @throws Exception
     */
    @Override
    public void decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {
        LogUtils.info("解码: remaining="+in.remaining() +",capacity="+in.capacity() +",limit="+in.limit()+",position="+in.position());// 设置的缓冲区的大小
        DecoderContext context = getContext(session);// 获取上下文对象
        context.appendToBuffer(in); // 接收到的数据包添加到上下文的buffer中
        IoBuffer contextBuffer = context.getBuffer();
        contextBuffer.flip(); // 指针置为0
        contextBuffer.mark(); // 做一下标记
        IoBuffer temp = IoBuffer.allocate(maxPackLength); // 中间临时缓冲，存放半包数据
        while (contextBuffer.remaining() >= packHeadLength){ // 至少要有包头
            // 读取包头(两个字段 length 和 flag 同时position往后移)
            int length = contextBuffer.getInt();
            byte flag = contextBuffer.get();

            if(length < 0 || length > maxPackLength){ // 无效数据包
                LogUtils.warn("无效数据包,length="+length);
                contextBuffer.reset();
                break;
            }else if(length >= packHeadLength && contextBuffer.remaining() >= length - packHeadLength ){ // 至少包含一个完整数据包
                int oldLimit = contextBuffer.limit(); // 保存当前的limit
                contextBuffer.limit(contextBuffer.position() + length - packHeadLength); // 设置本次循环要读这个完整数据包的limit(limit表示最多能读少数据)
                String content = contextBuffer.getString(context.getCharsetDecoder()); // 读取content（包头已经读取了这里读取content）
                LogUtils.info("position="+contextBuffer.position()); // 位置 （数据包的大小的整数倍）
                contextBuffer.limit(oldLimit); // 还原limit
                ProtocolPack pack = new ProtocolPack(flag, content); //组装成对象
                out.write(pack); // 发出去(给Handler)
            }else{ // 剩余数据不是一个完整的数据包
                temp.putInt(length); // 将已经读取的包头放到临时缓冲区
                temp.put(flag);
                break;
            }
        }
        if(contextBuffer.hasRemaining()){ // 还有数据(半包）
            temp.put(contextBuffer); // 将不完整的包体数据存入临时缓冲区
            temp.flip();
            contextBuffer.clear(); // 清空上下文缓冲区
            contextBuffer.put(temp); // 再把半包数据放入上下文缓冲区
        }else{
            contextBuffer.reset();// 重置为标记时的状态(mark)
        }
    }

    @Override
    public void dispose(IoSession session) throws Exception {
        DecoderContext context = (DecoderContext) session.getAttribute(CONTEXT);
        if(context != null){
            session.removeAttribute(CONTEXT);
        }
    }

    /**
     * 上下文
     * @param ioSession
     * @return
     */
    public DecoderContext getContext(IoSession ioSession){
        DecoderContext context = (DecoderContext) ioSession.getAttribute(CONTEXT);
        if(context == null){
            context = new DecoderContext(charset);
            ioSession.setAttribute(CONTEXT, context);
        }
        return context;
    }

    public int getMaxPackLength() {
        return maxPackLength;
    }

    public void setMaxPackLength(int maxPackLength) {
        if(maxPackLength < 0){
            throw  new IllegalArgumentException("maxPackLength 不能小于零:" + maxPackLength);
        }
        this.maxPackLength = maxPackLength;
    }
}
